from liwe.module import LiweModule
import sys

class Tags ( LiweModule ):

	def __init__ ( self, liwe ):
		super ( Tags, self ).__init__ ( liwe, "tags" )

		self.dbm = self.liwe.dbm

	#=====  USER TAG  ======
	#{{{ get_id_by_name ( tag_name, id_site, module )
	def get_id_by_name ( self, tag_name, id_site, module = None ):
		ids = self.get_ids_by_names ( tag_name, id_site, module, exp = 0 )
		if ids:
			return ids [ 0 ]

		return -1
	#}}}
	#{{{ get_by_id ( id_tag )
	def get_by_id ( self, id_tag ):
		res = None
		cur = self.dbm.single_row ( 'tag', [ 'id', 'name', 'created', 'uid' ], "id = '%s'" % id_tag )
		if cur:
			res = cur

		return res
	#}}}
	#{{{ get_ids_by_names ( tag_names, id_site, module, exp )
	def get_ids_by_names ( self, tag_name, id_site, module = None, exp = 0 ):
		tags_ids = []
		tags = [ t.strip () for t in tag_name.split ( "|" ) ]
		
		if exp:
			for tag in tags:
				ids = self.expand_tag ( tag, id_site, module )
				tags_ids += ids
		else:
			cond = []
			tables = [ "tag AS t" ]
			tags_to_search = "'" + "','".join ( tags ) + "'"
			cond.append ( " ( name IN ( %s ) )" % tags_to_search )
			#add id_site and module to conds i f not exp
			cond.append ( "t.id_site = %s" % id_site )
			if module:
				tables.append ( "tag_rel AS r" )
				cond.append ( "r.module = '%s'" % module )
				cond.append ( "t.id = r.id_tag" )

			vals = self.dbm.all_rows ( " , ".join ( tables ), "t.id", " AND ".join ( cond ), debug = self.debug () )

			if vals:
				for v in vals:
					tags_ids.append ( v [ 'id' ] )

		dct = {}
		for t in tags_ids:
			dct [ str ( t ) ] = 1

		lst = dct.keys ()
		if not lst and tag_name:
			lst = [ "-1" ]

		#self.pyhp.log.error ( "******* GET IDS BY NAMES ----- DCT: %s" % ( dct  ) )

		return lst
	#}}}
	#{{{ search ( tag_names, id_site, module, exp, exclude_tags = '' )
	def search ( self, tag_names, id_site, module = None, exp = 0, exclude_tags = '' ):
		tags_ids = self.get_ids_by_names ( tag_names, id_site, module, exp )
		#self.pyhp.log.error ( "******* GET IDS BY NAMES ----- DCT: %s" % ( tags_ids  ) )

		#sys.stderr.write ( "************  TAGS SEARCH ( %s, %s, %s, %s, %s )\n" % (  tag_names, id_site, module, exp, exclude_tags ) )

		exclude_ids = []
		exclude_objs = []
		if exclude_tags:
			# Prendo l'elenco degli id tag da escludere
			exclude_ids = self.get_ids_by_names ( exclude_tags, id_site, module, exp )

			if exclude_ids:
				# Prendo l'elenco degli id_obj relativi agli exclude_ids
				cur = self.dbm.select ( "tag_rel", "id_obj", 
					"id_tag IN ( " + ",".join ( exclude_ids ) + ") AND id_site = %s AND module = '%s'" % ( id_site, module ) )

				id_objs = {}
				for n in cur:
					id_objs [ str ( n [ 'id_obj' ] ) ] = 1

				exclude_objs = id_objs.keys ()

		if exp == 0:
			return self._search_exp_0 ( tags_ids, exclude_objs, id_site, module )

		return self._search_exp_1 ( tags_ids, exclude_objs, id_site, module )
	#}}}

	def _search_exp_0 ( self, tags_ids, exclude_objs, id_site, module ):
		cond = []

		#self.pyhp.log.debug ( "******* SEARCH EXP 0 ----- IDS: %s - EX_IDS: %s" % ( tags_ids, exclude_objs  ) )

		if id_site != -1:
			cond.append ( "id_site = '%s'" % id_site )

		if module:
			cond.append ( "module = '%s'" % module )

		flat_conds = self._mk_flat_conds ( tags_ids )
		if flat_conds:
			cond.append ( flat_conds )

		if exclude_objs:
			cond.append ( "id_obj NOT IN ( " + ','.join ( exclude_objs ) + ")" )

		# Prendo tutti gli id_obj che hanno TUTTI i tag specificati
		return self.dbm.all_rows ( "tag_flat", [ "id_obj", "tags" ], " AND ".join ( cond ) )

	"""
	def _search_exp_1 ( self, tags_ids, exclude_objs, id_site, module ):
		tables = [ 'tag AS t', 'tag_rel AS r' ]
		fields = [ 'r.id_obj' ]
		cond = [ "t.id = r.id_tag" ]

		if id_site != -1:
			cond.append ( "r.id_site = '%s'" % id_site )

		if tags_ids:
			s = []
			for tid in tags_ids:
				s.append ( "t.id = %s" % tid )

			cond.append ( "(" + " AND ".join ( s ) + ")" )
			
			#cond.append ( "t.id IN ( %s )" % ','.join ( [ str ( x ) for x in tags_ids ] ) )

		if exclude_objs:
			cond.append ( "r.id_obj NOT IN ( %s )" % ','.join ( exclude_objs ) )

		return self.dbm.all_rows ( tables, fields, " AND ".join ( cond ), distinct = 1 ) 
	"""

	def _search_exp_1 ( self, tags_ids, exclude_objs, id_site, module ):
		tables = [ 'tag_flat AS f' ]
		fields = [ 'f.id_obj' ]
		cond = []

		#sys.stderr.write ( "************* SEARCH EXP 1: %s %s %s %s\n" % ( tags_ids, exclude_objs, id_site, module ) )

		if id_site != -1:
			cond.append ( "f.id_site = '%s'" % id_site )

		if tags_ids:
			s = []
			for tid in tags_ids:
				s.append ( "f.tags  LIKE '%%:%s:%%'" % tid )

			cond.append ( "(" + " AND ".join ( s ) + ")" )
			
			#cond.append ( "t.id IN ( %s )" % ','.join ( [ str ( x ) for x in tags_ids ] ) )

		if exclude_objs:
			cond.append ( "f.id_obj NOT IN ( %s )" % ','.join ( exclude_objs ) )

		return self.dbm.all_rows ( tables, fields, " AND ".join ( cond ), distinct = 1 ) 
			

	#{{{ tags_list_all ( id_site, module = None, limit = 0 )
	def tags_list_all ( self, id_site, module = None, limit = 0 ):
		if not limit:
			return self._tags_list ( "all", None, id_site, module )

		# Nuova modalita' dove listo solo i primi "limit" tags piu' usati
		vals = self.dbm.all_rows ( "tag_rel", [ 'id_tag', 'count(id_tag) as c' ],
					   extras = "GROUP BY id_tag ORDER BY c DESC LIMIT %s" % limit )

		if not vals: return []

		res = self.dbm.all_rows ( "tag", [ "id", "name" ], 
					   "id in (%s)" % ','.join ( [ str ( v [ 'id_tag' ] ) for v in vals ] ), extras = "ORDER BY name" )


		return res

		


	#}}}
	#{{{ tags_list ( id_obj, id_site, mode = "all", module = None, id_only = 0, names_only = 0 )
	def tags_list ( self, id_obj, id_site, mode = "all", module = None, id_only = 0, names_only = 0, limit = 0 ):
		tags = self._tags_list ( mode, id_obj, id_site, module, limit )
		if id_only:
			return [ t [ 'id' ] for t in tags ]

		if names_only:
			return "|".join ( [ t [ 'name' ] for t in tags ] )

		return tags
	#}}}
	#{{{ _tags_list ( mode, id_obj, id_site, module = None )
	def _tags_list ( self, mode, id_obj, id_site, module = None, limit = 0 ):
		#TODO: gestire il mode ( solo i miei tag, oppure solo quelli degli altri ... )
		uid = self.liwe.pyhp.session.data.get ( "uid", 0 )
		
		cond = []

		#mode:
		#	- all: 		prende i tags di tutti
		#	- my:  		prende solo i tags dell'utente
		#	- others:	prende solo i tags degli altri

		tables = [ 'tag AS t' ]
		fields = [ 't.id', 't.name' ]
		cond = []
		rel = False

		if mode:
			if uid and mode == "my": cond.append ( "t.uid = %s" % uid )
			elif mode == "others": cond.append ( "t.uid <> %s" % uid )

		if id_obj:
			cond.append ( "r.id_obj = '%s'" % id_obj )
			rel = True
			

		if id_site != -1:
			cond.append ( "t.id_site = '%s'" % id_site )
			#rel = True

		if module:
			cond.append ( "r.module = '%s'" % module )
			rel = True

		if rel:
			cond.append ( "t.id = r.id_tag" )
			tables.append ( 'tag_rel AS r' )
			fields.append ( 'r.id_obj' )

		cond = " AND ".join ( cond )


		extras = "ORDER BY t.name"
		if limit :
			extras = "ORDER BY t.name LIMIT %s" % limit

		#return self.dbm.all_rows ( tables, fields, cond, distinct = 1 )
		res = self.dbm.all_rows ( tables, fields, cond, extras = extras, debug = self.debug() )
		if not res: res = []

		return res
	#}}}
	#{{{ expand_tag ( tag_name, id_site, module )
	def expand_tag ( self, tag_name, id_site, module ):
		ids = []

		cond = ""
		if id_site != -1: cond = " AND id_site = '%s'" % id_site
		# FIXME: forse la ricerca per modulo non va bene
		#if module: cond += " AND module = '%s'" % module

		tag_id = None
		tag = self.dbm.single_row ( 'tag', [ 'id' ], "name = '%s' %s" % ( tag_name, cond ) )
		if not tag: return ids

		tag_id = tag [ "id" ]

		#1. cerco tra i figli
		r = self.dbm.single_row ( [ 'tags_meta AS m' ], [ 'id_parent' ], "id_child = %s %s" % ( tag_id, cond ) )
		if r:
			id_parent = r [ "id_parent" ]
		else:
			id_parent = tag_id;


		ids.append ( id_parent )
		#2. ora cerco i figli del parent
		rs = self.dbm.all_rows ( [ 'tags_meta AS m' ], [ 'id_child' ], "id_parent = %s %s" % ( tag_id, cond ) )
		if rs:
			for r in rs:
				ids.append ( r [ "id_child" ] )

		return ids
	#}}}
	#{{{ list_affinity ( id_obj, id_site, module )
	def list_affinity ( self, id_obj, obj_tags, id_site, module, limit = 0 ):
		"""
		list_affinity ( id_obj, obj_tags, id_site, module, limit = 0 ) -> [ id_objs ]

		List all ``id_obj`` that have one or more tags in common with the ``obj_tags`` list.

		INPUT:
			- id_obj:	The original id_obj (this id_obj is excluded
		"""

		"""
		conds = [ "id_obj = '%s'" % id_obj ]
		if id_site: conds.append ( "id_site = '%s'" % id_site )
		if module:  conds.append ( "module  = '%s'" % module )

		# 1. Recupero gli id dei tag relativi all'id_obj passato
		vals = self.dbm.select ( "tag_rel", "id_tag", ' AND '.join ( conds ), distinct = 1 )
		if not vals:
			return []
		

		obj_tags = [ str ( t [ 'id_tag' ] ) for t in vals ]
		"""

		if limit: 
			limit = " LIMIT %d " % limit
		else:
			limit = ""
		
		# 1. Eseguo la ricerca per ranking
		vals = self.dbm.select ( "tag_rel", [ "id_obj", "COUNT(id_tag) AS rank" ],
					 "id_tag IN ( " + ','.join ( [ str ( n ) for n in obj_tags ] ) + ") AND id_obj <> '%s'" % id_obj,
					 extras = "GROUP BY id_obj ORDER BY rank DESC " + limit, debug = self.debug() )

		if not vals:
			return []

		return [ str ( t [ 'id_obj' ] ) for t in vals ]
	#}}}
	#{{{ get_relative_tags ( tags, id_site, module )
	def get_relative_tags ( self, tags, id_site, module ):
		base_conds = []

		if id_site != -1:
			base_conds.append ( "id_site = %s" % id_site )

		# FIXME: forse la ricerca per modulo non va bene
		"""
		if module:
			base_conds.append ( "module = '%s'" % module )
		"""

		#ids = [ str ( x ) for x in self.get_ids_by_names ( tags, id_site, module, exp = 0 ) ]
		ids = self.get_ids_by_names ( tags, id_site, module, exp = 0 ) 

		base_conds.append ( self._mk_flat_conds ( ids ) )
		conds = " AND ".join ( base_conds )

		# Prendo tutti gli id_obj che hanno TUTTI i tag specificati
		vals = self.dbm.all_rows ( "tag_flat", [ "id_obj", "tags" ], conds )

		if not vals: return []

		# Prendo tutti gli id_tag degli id_obj che mi sono stati restituiti
		id_tags = {}
		for v in vals:
			for t in v [ 'tags' ].split ( ":" ) [ 1 : -1 ]:
				id_tags [ t ] = str ( t )

		# Cancello le chiavi "vecchie" (delle query precedenti)
		for i in ids:
			if i in id_tags: 
				del id_tags [ i ]

		# Restituisco la lista di dizionari ( id, nome_tag )
		return self.dbm.all_rows ( "tag", [ "id", "name" ], "id IN (" + ",".join ( id_tags.values () ) + ")", extras = "ORDER BY name" )
	#}}}

	def _mk_flat_conds ( self, ids ): #, exclude_ids = None ):
		# Karma: questa non la capirai mai ;-)

		#self.pyhp.log.debug ( "******* IDS: %s - EX_IDS: %s" % ( ids, exclude_ids  ) )

		if not ids: return ""

		conds = "(" + " AND ".join ( [ "tags LIKE '%%:%s:%%'" % x for x in ids ] ) # + ")"

		"""
		if exclude_ids:
			if ids: conds += " AND "
			conds += " AND ".join ( [ "tags NOT LIKE '%%:%s:%%'" % x for x in exclude_ids ] ) # + ")"
		"""

		conds += ")"

		return conds

	#===== ADMIN  TAG =====
	#{{{ tag_object ( id_obj, id_site, tags, module = "" )
	def tag_object ( self, id_obj, id_site, tags, module = "" ):
		uid = self.liwe.pyhp.session.data.get ( "uid", 0 )

		cond = ""
		if id_site != -1: cond = " AND id_site = %s" % id_site

		lst = [ x.strip ().replace ( "'", "" ) for x in tags.split ( "|" ) ]
		tags_to_search = "'" + "','".join ( lst ) + "'"
		dct = {}
		cur = self.dbm.select ( 'tag', [ 'id', 'name' ], 'name IN ( %s ) %s' % ( tags_to_search, cond ) )
		if cur:
			for t in cur:
				if t [ 'name' ] and len ( t [ 'name' ] ) > 0 :
					dct [ t [ 'name' ] ] = t [ 'id' ]

			cur.close ()

		keys = dct.keys ()
		for x in lst:
			if x not in keys:
				dct [ x ] = self._add_tag ( x, id_site )

		for tag_name, id_tag in dct.iteritems ():
			cond = "id_obj = '%s' AND id_tag = '%s' AND id_site = '%s'" % ( id_obj, id_tag, id_site )
			if module: cond += " AND module = '%s'" % module

			self.dbm.delete ( 'tag_rel', cond )
			data = { "id_obj" : id_obj, "uid" : uid, "id_tag" : id_tag, "id_site" : id_site, "module": module }

			self.dbm.insert ( 'tag_rel', data )

		self._update_obj ( id_obj, id_site, module )
	#}}}
	#{{{ _add_tag ( tag_name, id_site )
	def _add_tag ( self, tag_name, id_site = None ):
		if not tag_name or len ( tag_name.strip () ) <= 2 :
			return

		tag_name = tag_name.strip ()

		uid = self.liwe.pyhp.session.data.get ( "uid", 0 )

		if id_site == -1:
			id_site = self.site_id

		tag_id = None
		tag = self.dbm.single_row ( 'tag', [ 'id' ], "name = '%s' AND id_site = %s" % ( tag_name, id_site ) )
		if tag:
			tag_id = tag [ "id" ]
		else:
			self.dbm.insert ( 'tag', { "uid": uid, "created" : "*NOW()", "name" : tag_name, "id_site" : id_site } )
			tag_id = self.dbm.insert_id()

		return tag_id
	#}}}
	#{{{ del_tag ( tag_name, id_obj, id_site, module )
	def del_tag ( self, tag_name, id_obj, id_site, module = None ):
		#1. prelevo l'id del tag
		tag_id = self.get_id_by_name ( tag_name, id_site, module )
		if not tag_id: return

		cond = "id_obj = '%s' AND id_tag = '%s' AND id_site = '%s'" % ( id_obj, tag_id, id_site )
		if module: cond += " AND module = '%s'" % module

		#2. cancello dalla tabella delle associazioni
		self.dbm.delete ( 'tag_rel', cond )

		self._update_obj ( id_obj, id_site, module )

		count = self.dbm.count ( 'tag_rel', '*', cond )

		if count != 0:
			return

		cond = "( id_parent = '%s' OR id_child = '%s' ) AND id_site = '%s'" % ( tag_id, tag_id, id_site )
		if module: cond += " AND module = '%s'" % module
		#3. cancello i meta-tags
		self.dbm.delete ( 'tags_meta', cond )
	#}}}

	#{{{ del_all_tags ( id_obj, id_site, module )
	def del_all_tags ( self, id_obj, id_site, module = None ):
		#1 costruisco le condizioni
		cond = "id_obj = '%s' AND id_site = '%s'" % ( id_obj, id_site )
		if module: cond += " AND module = '%s'" % module

		#2. cancello dalla tabella delle associazioni
		self.dbm.delete ( 'tag_rel', cond )

		#3. cancello dalla tabella tag_flat
		self.dbm.delete ( 'tag_flat', cond )

		self._update_obj ( id_obj, id_site, module )
	#}}}

	#====  USER META ====
	#{{{ list_meta_parent ( id_site, module = None )
	def list_meta_parent ( self, id_site, module = None ):
		cond = "m.id_site = '%s' AND t.id = m.id_parent" % id_site
		if module: cond += " AND module = '%s'" % module

		return self.dbm.all_rows ( [ 'tags_meta AS m', 'tag AS t' ], [ 't.id', 't.uid', 't.created', 't.name' ], cond, distinct = 1 )
	#}}}
	#{{{ list_meta_by_parent ( parent_name, id_site, module = None )
	def list_meta_by_parent ( self, parent_name, id_site, module = None ):
		id_parent = self.get_id_by_name ( parent_name, id_site, module )

		cond = "m.id_parent = %s AND m.id_site = '%s' AND t.id = m.id_child" % ( id_parent, id_site )
		if module: cond += " AND module = '%s'" % module

		return self.dbm.all_rows ( [ 'tags_meta AS m', 'tag AS t' ], [ 't.id', 't.uid', 't.created', 't.name' ], cond )
	#}}}

	#====  ADMIN META ====
	#{{{ add_meta ( tag1, tag2, id_site, module = None )
	def add_meta ( self, tag1, tag2, id_site, module = None ):
		#1. aggiungo (eventualmente) i tag
		id1 = self._add_tag ( tag1, id_site )
		id2 = self._add_tag ( tag2, id_site )

		cond = " AND id_site = '%s'" % id_site
		if module: cond += " AND module = '%s'" % module

		#2. cerco id1 tra i figli
		r = self.dbm.single_row ( [ 'tags_meta' ], [ 'id_parent' ], "id_child = %s %s" % ( id1, cond ) )
		if r:
			# aggiungo id2 tra i figli
			id_parent = r [ "id_parent" ]
			id_child = id2;
		else:
			#cerco id2 tra i figli
			r = self.dbm.single_row ( [ 'tags_meta' ], [ 'id_parent' ], "id_child = %s %s" % ( id2, cond ) )
			if r:
				# aggiungo id1 tra i figli
				id_parent = r [ "id_parent" ]
				id_child = id1
			else:
				# aggiungo una NUOVA relazione
				id_parent = id1
				id_child = id2

		#3. guardo se la COPPIA c'e' gia'
		r = self.dbm.single_row ( [ 'tags_meta' ], [ 'id_parent' ], "id_parent = %s AND id_child = %s %s" % ( id_parent, id_child, cond ) )
		if r:
			return

		#4. aggiungo nella tabella dei meta-tag
		self.dbm.insert ( 'tags_meta', { "id_parent" : id_parent, "id_child": id_child, "module": module, "id_site": id_site } )
	#}}}
	#{{{ del_meta ( tag1, tag2, id_site, module = None )
	def del_meta ( self, tag1, tag2, id_site, module = None ):
		cond = " AND id_site = '%s'" % id_site
		if module: cond += " AND module = '%s'" % module

		id1 = self.get_id_by_name ( tag1, id_site, module )
		id2 = self.get_id_by_name ( tag2, id_site, module )

		if not id1 or not id2:
			return

		self.dbm.delete ( 'tags_meta', "( id_parent = %s AND id_child = %s ) OR ( id_parent = %s AND id_child = %s ) %s" % ( id1, id2, id2, id1, cond ) )
	#}}}

	def _update_obj ( self, id_obj, id_site, module = "" ):
		conds = [
			"id_obj = '%s'" % id_obj,
			"id_site = %s" % id_site
		]

		if module: conds.append ( "module = '%s'" % module )

		conds = ' AND '.join ( conds )

		# Cancello l'eventuale riga in tag_flat
		self.dbm.delete ( "tag_flat", conds )

		vals = self.dbm.all_rows ( "tag_rel", [ "id_tag" ], conds )
		if not vals: return

		id_tags = []
		for v in vals:
			id_tags.append ( str ( v [ 'id_tag' ] ) )

		tags = ':' + ':'.join ( id_tags ) + ':'
		self.dbm.insert ( 'tag_flat', { "id_obj" : id_obj, "id_site" : id_site, "module" : module, "tags" : tags  } )

